Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 << zurück
Visual C# 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual C# 2005

Visual C# 2005
1.320 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-586-X
gp Kapitel 5 Das Klassendesign (Teil 2)
  gp 5.1 Statische Klassenkomponenten
    gp 5.1.1 Die Realisierung eines Objektzählers
    gp 5.1.2 Zugriff auf statische Komponenten
    gp 5.1.3 Statische Klassenvariable in der Klasse »Circle«
    gp 5.1.4 Klassenspezifische Methoden
    gp 5.1.5 Statische Methoden in der Klasse »Circle«
    gp 5.1.6 Statische Klasseninitialisierer
    gp 5.1.7 Statische Klassen
    gp 5.1.8 Zusammenfassung
  gp 5.2 Delegate – Methodenzeiger unter .NET
    gp 5.2.1 Einführung in das Prinzip der Delegate
    gp 5.2.2 Zusammenfassung der Arbeitsschritte
    gp 5.2.3 Vereinfachter Aufruf eines Delegaten
    gp 5.2.4 Anonyme Methoden
  gp 5.3 Ereignisse eines Objekts
    gp 5.3.1 Ergänzung eines Ereignisses in einer Ereignisquelle
    gp 5.3.2 Die Behandlung eines Ereignisses im Ereignisempfänger
    gp 5.3.3 Wenn der Ereignisempfänger ein Ereignis nicht behandelt
    gp 5.3.4 Ereignisse mit Übergabeparameter
    gp 5.3.5 Zusammenfassung
  gp 5.4 Strukturen – eine Sonderform der Klassen
    gp 5.4.1 Die Definition einer Struktur
    gp 5.4.2 Initialisieren einer Struktur
    gp 5.4.3 Weitere Merkmale einer Struktur
    gp 5.4.4 Verschachtelung von Strukturen
    gp 5.4.5 Änderung der Klasse »Circle«
    gp 5.4.6 Zusammenfassung
  gp 5.5 Enumerationen (Aufzählungen)
    gp 5.5.1 Wertzuweisung an enum-Mitglieder
    gp 5.5.2 Alle Mitglieder einer Aufzählung durchlaufen
  gp 5.6 Referenz- und Wertetypen
    gp 5.6.1 Typumwandlung mit Boxing
    gp 5.6.2 Die Unboxing-Konvertierung
    gp 5.6.3 Zusammenfassung
  gp 5.7 Namensräume (Namespaces)
    gp 5.7.1 Zugriff auf Namespaces
    gp 5.7.2 Die »using«-Direktive
    gp 5.7.3 Vermeiden von Mehrdeutigkeiten
    gp 5.7.4 Aufrufe mit dem »::«-Operator umleiten
    gp 5.7.5 Namespaces festlegen
    gp 5.7.6 Zusammenfassung


Galileo Computing

5.2 Delegate – Methodenzeiger unter .NET  downtop

Das Prinzip der Delegate ist nicht ganz neu, wohl aber der Begriff an sich. »Delegate« ist das englische Wort für »delegieren« – etwas »weiterleiten«. Tatsächlich leitet ein Delegat weiter, er leitet nämlich einen Methodenaufruf an eine bestimmte Methode weiter.


Galileo Computing

5.2.1 Einführung in das Prinzip der Delegate  downtop

Die Technik, die sich dahinter verbirgt, wird in der Sprache C auch als Funktionszeiger bezeichnet. Zeigertechnik und .NET – das passt eigentlich nicht zusammen. Die Zeigertechnik – so interessant sie auch sein mag – birgt einige Nachteile in sich: Sie ist schwierig zu lernen, sie ist sehr komplex, die Programme sind zu kompliziert. Ein falscher Einsatz führt nicht selten zu Speicherzugriffsfehlern und damit zum Absturz eines laufenden Programms. Nicht umsonst haben die .NET-Entwickler (und auch die von Java) die Zeigertechnologie gemieden wie der Teufel das Weihwasser.

Dennoch gibt es Aufgabenstellungen, bei denen kein Weg an der Zeigertechnik vorbeiführt – auch nicht unter .NET, wenn auch in einer nicht sofort offensichtlichen Form. Wie Sie wissen, basiert ausnahmslos alles im .NET Framework auf Objekten. Da verwundert es nicht, dass auch die Methodenzeiger in ein Objekt verpackt und als Delegat bezeichnet ihren Weg in die Laufzeitumgebung finden.


Ein Delegat ist ein Objekt, das den Zeiger auf eine Objektmethode beschreibt.

Bevor wir uns mit den Details von Delegaten beschäftigen, wollen wir uns zunächst an einem einfachen Beispiel die grundsätzliche Arbeitsweise verdeutlichen.

Die Operation, die von diesem Code ausgeführt wird, ist recht einfach: Der Anwender gibt zwei Zahlen an der Konsole ein und hat anschließend die Wahl, ob beide Zahlen addiert oder subtrahiert werden sollen. Das Resultat der Operation wird abhängig von der Wahl des Anwenders an der Konsole ausgegeben.


// --------------------------------------------------------------
// Beispiel: ...\Kapitel 5\EinfacherDelegat
// --------------------------------------------------------------
class Program {
  // Definition des Delegaten
  public delegate double ProcessOperation(double dblVar1, double dblVar2);
  static void Main(string[] args) {
    // Variable vom Typ des Delegaten
    ProcessOperation  process;
    // Eingabe der Operanden
    Console.Write("Geben Sie den ersten Operanden ein: ");
    double input1 = Convert.ToDouble(Console.ReadLine());
    Console.Write("Geben Sie den zweiten Operanden ein: ");
    double input2 = Convert.ToDouble(Console.ReadLine());
    // Wahl der Operation
    Console.Write("Welche Operation wollen Sie ausführen?");
    Console.WriteLine("Addition – (A)");
    Console.WriteLine("Subtraktion – (S)");
    string wahl = Console.ReadLine().ToUpper();
    // in Abhängigkeit von der Wahl des Anwenders wird die
    // Variable 'process' mit einem Zeiger auf die 
    // auszuführende Methode initialisiert
    if(wahl == "A")
      process = new ProcessOperation(Addition);
    else if(wahl == "S")
      process = new ProcessOperation(Subtraktion);
    else {
      Console.Write("Ungültige Eingabe");
      Console.ReadLine();
      return;
    }
    // Aufruf der Operation 'Addition' oder 'Subtraktion' 
    // über den Delegaten
    double result = process(input1, input2);
    Console.WriteLine("----------------------------------");
    Console.Write("Ergebnis = {0}",result);
    Console.ReadLine();
  }
  public static double Addition(double x, double y) {
    return x + y;
  }
  public static double Subtraktion(double x, double y) {
    return x – y;
  }
}

In der Klasse Class1 sind neben dem obligatorischen Einstiegspunkt in die Laufzeit zwei weitere statische Methoden definiert, die aus Main heraus aufgerufen werden und die beiden Operationen Addition und Subtraktion beschreiben.

Die Wahl, ob die beiden Zahlen addiert oder subtrahiert werden sollen, trifft der Anwender durch die Eingabe von »A« oder »S« an der Konsole. Um die Eingabe in Kleinschreibweise ebenfalls zu berücksichtigen, wird die Eingabe mit der Methode ToUpper der Klasse String in Großschreibweise umgewandelt.


string wahl = Console.ReadLine().ToUpper();

Nachdem der Anwender seine Wahl getroffen hat, muss zuerst überprüft werden, wie diese ausgefallen ist, um entsprechend im Programmcode zu reagieren. Vermutlich hätten Sie eine solche Aufgabenstellung bisher wie folgt gelöst:


double result;
if(wahl == "A")
  result = Addition(input1, input2);
else if(wahl == "S")
  result = Subtraktion(input1, input2);

Es gibt keinen Zweifel daran, dass diese Implementierung natürlich auch zum richtigen Ergebnis führt. Das Resultat der Operation wird in den Anweisungsblöcken hinter if bzw. else if abgerufen.

Nun betrachten wir die entscheidenden Anweisungen der Lösung im Beispiel EinfacherDelegate:


if(wahl == "A")
  process = new ProcessOperation(Addition);
else if(wahl == "S")
  process = new ProcessOperation(Subtraktion);
...
double result = process(input1, input2);

Der Unterschied ist jetzt, dass wir das Ergebnis der Addition bzw. Subtraktion nun nicht mehr in den beiden Anweisungsblöcken der if-Struktur abrufen, sondern außerhalb derselben. Da aber außerhalb der if-Struktur die Wahl des Anwenders nicht bekannt sein kann, stehen wir vor der Frage, wie es möglich ist, eine Methode an einem Allgemeinplatz dynamisch aufrufen zu können. Die Antwort darauf ist prinzipiell nicht schwierig: Wir müssen einen Verweis auf die aufzurufende Methode in einer Variablen speichern, der später an einer beliebigen Stelle im Code ausgewertet werden kann.

Bisher kennen wir Verweise nur im Zusammenhang mit Objekten. Mit Objektverweisen werden zusammenhängende Datenblöcke im Hauptspeicher adressiert, in denen die Zustandsdaten eines ganz bestimmten Objekts beschrieben werden. Ein Verweis auf Programmcode ist im Grunde genommen nicht anders, zeigt aber auf Byte-Sequenzen, die anders interpretiert werden müssen – nämlich als ausführbarer Programmcode. Damit ist auch klar, dass ein Verweis auf Code anders definiert werden muss als der uns gebräuchliche Verweis auf Datenblöcke. Aus diesem Grund wurden in .NET die Delegaten eingeführt.

Wie schon oben erwähnt, kapselt ein Delegat den Zeiger auf eine Methode. Wir wissen zudem, dass .NET nur Objekte kennt. Sehen wir uns jetzt an, wie die Konstrukteure von C# diese beiden Anforderungen konzeptuell gelöst haben.

Im Code des Beispiels EinfacherDelegat wird mit


public delegate double ProcessOperation(double dblVar1, double dblVar2);

ein Delegat definiert. Diese Definition erinnert ein wenig an die Methodensignatur einer Methode namens ProcessOperation, die zwei Parameter vom Typ double empfängt und als Rückgabewert einen double liefert – nur ergänzt um das Schlüsselwort delegate.


Hinweis   Ein Delegat kann sowohl innerhalb als auch außerhalb einer Klasse definiert werden. Ein in einer Klasse definierter Delegat ist immer an die Klasse gebunden.

Ein Delegat kapselt den Zeiger auf eine Methode, oder mit anderen Worten, er steht für einen beliebigen Methodenaufruf. Ganz beliebig ist der Methodenaufruf allerdings nicht, denn jede Methode hat eine exakt definierte Parameterliste mit Parametern eines bestimmten Typs. Ein Delegat beschreibt einen Zeiger auf eine Methode, wobei die Typen der Parameterliste der Methode, auf die der Delegat zeigt, mit der Parameterliste der delegate-Definition übereinstimmen muss.

In unserem Beispiel werden in der Parameterliste des Delegaten ProcessOperation zwei Parameter vom Typ double aufgeführt. Damit wäre dieser Delegat in der Lage, jede x-beliebige Methode eines x-beliebigen Objekts aufzurufen – vorausgesetzt, die Methode definiert eine Parameterliste, die genau zwei double-Argumente erwartet.


Die Parameterliste einer Delegat-Definition entspricht der Parameterliste der Methode, auf die der Delegat zeigt.

Das ist nicht die einzige Bedingung, die an die Methode gestellt wird, die ein Delegat beschreibt. Der Rückgabewert spielt eine ebenso wichtige Rolle. Im Beispiel des Delegaten ProcessOperation muss die Methode in jedem Fall einen Rückgabewert vom Typ double haben. Weil sowohl Parameterliste als auch Rückgabetyp durch einen Delegaten eindeutig festgelegt werden, spricht man beim Konstrukt eines Delegaten auch von einem typisierten Funktionszeiger.

Nicht jede Methode hat einen Rückgabewert. Beabsichtigen Sie beispielsweise, einen Delegaten zu definieren, der in der Lage ist, einen Zeiger auf sämtliche Methoden zu beschreiben, die parameterlos sind und keinen Rückgabewert haben, sähe die Definition folgendermaßen aus:


public delegate void MyDelegate();

Sie können die Definition eines Delegaten mit der Definition einer Klasse vergleichen, denn beide beschreiben einen Typ. Um ein konkretes Objekt zu erhalten, muss zuerst eine Variable vom Typ der Klasse deklariert werden – das ist bei einem Delegaten nicht anders. Im Beispiel dient dazu die Anweisung:


ProcessOperation process;

Damit ist die Variable process vom Typ ProcessOperation deklariert, aber noch nicht initialisiert. Mit anderen Worten: process ist ein Delegat und kann auf eine Methode verweisen, die zwei double-Argumente erwartet und einen double als Resultat des Aufrufs zurückliefert. In diesem Moment weiß der Delegat allerdings noch nicht, um welche Methode es sich dabei genau handelt.

Die Initialisierung erfolgt – analog zur Instanziierung einer Klasse – mit dem Operator new unter Angabe des Delegatentyps. Dahinter gibt man in runden Klammern den Bezeichner der Methode an, die später vom Delegaten aufgerufen werden soll. In unserem Beispiel handelt es sich um


process = new ProcessOperation(Addition);

und


process = new ProcessOperation(Subtraktion);

Danach ist dem Delegaten bekannt, welche Methode ausgeführt werden soll: entweder Addition oder Subtraktion. Allerdings wird die Methode, auf die der Delegat in Form eines Zeigers verweist, noch nicht sofort gestartet, denn dazu bedarf es eines Anstoßes durch den Aufruf des Delegaten:


double result = process(input1, input2);

Der Aufruf erinnert an den Aufruf einer Methode, dabei wird allerdings der Methodenname (hier: Addition bzw. Subtraktion) durch die Variable vom Typ des Delegaten ersetzt. In den Klammern werden die erforderlichen Argumente an die Methode übergeben.


Galileo Computing

5.2.2 Zusammenfassung der Arbeitsschritte  downtop

Um eine aufzurufende Methode erst zur Laufzeit festzulegen, ist das Konstrukt der Delegaten sicherlich sehr interessant. Allerdings ist die syntaktische Realisierung etwas gewöhnungsbedürftig, daher sollen hier die Schritte noch einmal allgemein zusammengefasst werden.

1.  Definieren Sie zuerst einen Delegaten, z.B.:
public delegate double ProcessOperation(double dblVar1, double dblVar2);
    Als Modifizierer kommen private, protected, internal und public sowie der Modifizierer new in Frage. Letzterer wird in Kapitel 6 behandelt.
       
2. Deklarieren Sie eine Variable vom Typ des Delegaten, z.B.:
       
ProcessOperation process;
3. Erzeugen Sie ein Objekt vom Typ des Delegaten, und übergeben Sie dabei als Argument den Namen der Methode, die vom Delegaten aufgerufen werden soll, z.B.:
       
process = new ProcessOperation(Addition);
4. Rufen Sie den Delegaten auf, und übergeben Sie dabei die Parameter, die von der Methode empfangen werden sollen, auf die der Delegat zeigt. z.B.:
       
double result = process(input1, input2);
Hinweis   Wir haben noch nicht alle Gesichtspunkte erörtert, die im Zusammenhang mit den Delegaten von programmiertechnischem Interesse sind. Wenn wir uns jedoch weiter mit diesen Konstrukten beschäftigen wollen, sollten mehr Grundsätze der objektorientierten Programmierung bekannt sein. Daher wird das Thema an dieser Stelle unterbrochen und in Kapitel 7 noch einmal aufgegriffen.


Galileo Computing

5.2.3 Vereinfachter Aufruf eines Delegaten  downtop

Seit der Version 2.0 gibt es unter C# eine neue Notation, einen Delegaten zu instanzîieren und ihm gleichzeitig die auszuführende Methode anzugeben. Diese Neuerung ist etwas einfacher in der Handhabung und erspart uns ein wenig Tipparbeit. Sie können nämlich anstelle der Anweisung


ProcessOperation process = new ProcessOperation(Addition);

auch wie folgt codieren:


ProcessOperation process = Addition;


Galileo Computing

5.2.4 Anonyme Methoden  toptop

Delegate haben wir bisher in der Weise konstruiert, indem wir den Delegaten instanziiert und ihm als Parameter eine benannte Methode übergeben haben. Das setzt voraus, dass die Methode auch irgendwo im Programmcode namentlich definiert werden muss. Bei größeren Anwendungen könnte das dazu führen, dass sehr viele Methoden nur dazu dienen, einzig und allein einen bestimmten Delegaten zu bedienen. Der Code wird unübersichtlich.

Eine Ergänzung von C# 2.0 ist, dass der auszuführende Programmcode direkt mit dem Delegaten verknüpft werden kann. Der Code ist nicht mehr mit einem Methodenbezeichner namentlich verbunden und wird deshalb als anonyme Methode bezeichnet.

Wir wollen uns das an einem Beispiel ansehen. Des besseren Vergleichs wegen wird der Delegat zuerst mit einer benannten Methode verknüpft, anschließend wird derselbe Code als anonyme Methode implementiert.


// Delegate-Deklaration
public delegate long MyDelegate(int x, int y);
class Program {
  static void Main(string[] args) {
    // Verknüpfung des Delegaten mit einer benannten Methode
    MyDelegate del = new MyDelegate(Program.Addition);
    Console.WriteLine("Ergebnis = {0}", del(13, 34));
    Console.ReadLine();
  }
  static long Addition(int value1, int value2) {
    Console.Write("Die Parameterwerte sind: ");
    Console.WriteLine("{0} und {1}", value1, value2);
    return value1 + value2;
  }
}

Der Code sollte ohne Probleme verständlich sein, deshalb werden wir uns sofort der Variante mit der anonymen Methode zuwenden. Im Unterschied dazu wird der Code, der oben in der Methode Addition implementiert ist, jetzt direkt nach der Deklaration der Variablen vom Typ des Delegaten angegeben.


// Delegate-Deklaration
public delegate long MyDelegate(int x, int y);
class Program {
  static void Main(string[] args) {
    MyDelegate del = delegate(int value1, int value2)
    // anonyme Methode
    {
      Console.Write("Die Parameterwerte sind: ");
      Console.WriteLine("{0} und {1}", value1, value2);
      return value1 + value2;
    };
    Console.WriteLine("Ergebnis = {0}", del(13, 34));
    Console.ReadLine();
  }
}

Um einen Delegaten zu instanziieren und mit dem Rumpf einer anonymen Methode zu verbinden, dient ebenfalls das Schlüsselwort delegate, hinter dem eine Parameterliste entsprechend der Delegatendefinition angegeben ist. Handelt es sich um eine parameterlose, anonyme Methode, bleibt die Liste leer, und auf die Angabe der optionalen runden Klammern kann auch verzichtet werden.

Die allgemeine Syntax, mit der das Objekt eines Delegaten mit einer anonymen Methode verknüpft wird, lautet wie folgt:


delegate [(Parameterliste)] {Anweisungsblock};

Da sich der Anweisungsblock einer anonymen Methode immer innerhalb einer »äußeren« Methode befindet, kann aus der anonymen Methode heraus auf jede andere Variable zugegriffen werden. Es gelten dabei die üblichen Regeln der Sichtbarkeit.

Anonyme Methoden unterliegen im Vergleich zu anderen Anweisungsblöcken nur einer Einschränkung: Mit den Sprunganweisungen continue, break und goto darf innerhalb einer anonymen Methode nicht zu einer Anweisung verzweigt werden, die außerhalb der anonymen Methode codiert ist. Ebenfalls unzulässig ist eine Sprunganweisung außerhalb einer anonymen Methode, deren Ziel innerhalb einer anonymen Methode zu finden ist.

 << zurück
  
  Zum Katalog
Zum Katalog: Visual C# 2005
Visual C# 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Visual Basic 2005






 Visual Basic 2005


Zum Katalog: Java ist auch eine Insel






 Java ist auch eine
 Insel


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de